package nba2kmodtool.filedesc;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import nba2kmodtool.util.ByteFileReader;

class N2kIffFileReader {
	private static final boolean IS_DEBUG = BaseIffFile.IS_DEBUG;

	private BaseIffFile iff;

	N2kIffFileReader(BaseIffFile iff) {
		this.iff = iff;
	}

	BaseIffFile read(ByteFileReader in) throws IOException {
		iff.headerSize = in.read4BytesAsInt();
		iff.fileSize = in.readBytesAsLong(8, true);
		iff.fileBlockNum = in.read4BytesAsInt();
		iff.blocks = new IffBlock[iff.fileBlockNum];
		in.skipBytes(4);
		iff.fileNum = in.read4BytesAsInt();
		iff.dataFiles = new IffDataFile[iff.fileNum];
		long addrBlockSize = in.read4Bytes();
		if (IS_DEBUG) {
			System.out.printf("Cur:%X\n", in.getFilePointer());
			System.out.printf("HeadSize:%X, FileSize:%X, BlockNum:%d, FileNum:%d, AddrBlockSize:%X\n", iff.headerSize,
					iff.fileSize, iff.fileBlockNum, iff.fileNum, addrBlockSize);
		}
		//--
		long decomSize = iff.headerSize;
		for (int i = 0; i < iff.fileBlockNum; i++) {
			iff.blocks[i] = new IffBlock();
			iff.blocks[i].blockSerial = in.read4Bytes();
			in.skipBytes(4);
			in.skipBytes(4);
			iff.blocks[i].blockSize = in.read4Bytes();
			iff.blocks[i].partNum = in.read4BytesAsInt();
			iff.blocks[i].blockAddr = in.read4Bytes();
			iff.blocks[i].compressedSize = in.read4Bytes();
			decomSize += iff.blocks[i].blockSize;
			iff.blocks[i].isCompressed = iff.blocks[i].blockSize != iff.blocks[i].compressedSize;
			if (iff.isCompressed == false) {
				iff.isCompressed = iff.blocks[i].isCompressed;
			}
			iff.blocks[i].indexType = in.read4BytesAsInt();
			if (iff.blocks[i].indexType != 0) {
				iff.indexedBlock = i;
			}
			if (IS_DEBUG) {
				System.out.printf(" - Block:%d, Serial:%X, DateSize:%X, Addr:%X, CompressSize:%X\n", i,
						iff.blocks[i].blockSerial, iff.blocks[i].blockSize, iff.blocks[i].blockAddr,
						iff.blocks[i].compressedSize);
			}
		}
		//return to decompress
		if (iff.isCompressed) {
			return iff;
		}
		if (iff.indexedBlock >= 0) {
			this.readBlockIndex(in);
		}
		long nextMark = in.read4Bytes();
		if (IS_DEBUG) {
			System.out.printf("Cur:%X, NextMark:%X\n", in.getFilePointer(), nextMark);
		}
		in.skipBytes((int) (nextMark - 5));
		//--
		List<Long> list = new ArrayList<Long>();
		long x = 0;
		int num;
		List<Long> addrs = new ArrayList<Long>();
		for (int i = 0; i < iff.fileNum; i++) {
			List<Long> tempList = new ArrayList<Long>();
			IffDataFile dataFile = new IffDataFile();
			dataFile.fileSerial = in.read4Bytes();
			dataFile.fileType = in.read4Bytes();
			num = in.read4BytesAsInt();
			dataFile.chunkAddr = new long[num];
			if (IS_DEBUG) {
				System.out.printf(" - Cur:%X, SubFile:%d, FileSerial:%X, FileType:%X, Num:%d\n", in.getFilePointer(),
						i, dataFile.fileSerial, dataFile.fileType, num);
			}
			int isvalid = 0;
			for (int j = 0; j < num; j++) {
				x = in.read4Bytes();
				dataFile.chunkAddr[j] = x;
				addrs.add(x);
				if (IS_DEBUG) {
					System.out.printf("readPartAddr-%d: %X\n", j, x);
				}
				if (x > decomSize) {
					isvalid = 1;
					continue;
				}
				boolean isIndexed = false;
				if (iff.blocks[j].indexType > 0) {
					Map<Long, IffBlockIndex> indexMap = iff.blocks[j].indexMap;
					IffBlockIndex index = indexMap.get(x);
					if (index != null) {
						if (IS_DEBUG) {
							System.out.printf("%d - %X - indexed", i, x);
						}
						x = index.getStartAddr();
						dataFile.indexType = iff.blocks[j].indexType;
						dataFile.index = index;
						isIndexed = true;
					}
				}
				if (isIndexed == false) {
					x += iff.blocks[j].blockAddr + isvalid;
				}
				isvalid = 0;
				list.add(x);
				tempList.add(x);
			}
			dataFile.partNum = tempList.size();
			dataFile.partAddr = new long[tempList.size()];
			dataFile.partSize = new long[tempList.size()];
			if (IS_DEBUG) {
				System.out.printf(" + SubFile:%d, PartNum:%d:\n", i, dataFile.partNum);
			}
			for (int p = 0; p < tempList.size(); p++) {
				dataFile.partAddr[p] = ((Long) tempList.get(p)).longValue();
			}
			iff.dataFiles[i] = dataFile;
		}
		if (IS_DEBUG) {
			for (int i = 0; i < iff.fileNum; i++) {
				IffDataFile dataFile = iff.dataFiles[i];
				for (int j = 0; j < dataFile.chunkAddr.length; j++) {
					System.out.printf("f:%d - p:%d %X\n", i, j, dataFile.chunkAddr[j]);
				}
			}
		}
		list.add(decomSize);
		long[] lt = new long[list.size()];
		for (int i = 0; i < list.size(); i++) {
			lt[i] = ((Long) list.get(i)).longValue();
		}
		Arrays.sort(lt);
		int index = 0;
		long partAddress;
		long partSize = 0;
		for (int i = 0; i < iff.fileNum; i++) {
			IffDataFile dataFile = iff.dataFiles[i];
			for (int j = 0; j < iff.dataFiles[i].partNum; j++) {
				partAddress = dataFile.partAddr[j];
				index = Arrays.binarySearch(lt, partAddress);
				partSize = lt[index + 1] - partAddress;
				dataFile.partSize[j] = partSize;
				if (IS_DEBUG) {
					System.out.printf("%d - part:%d - addr:%X + %X=%X\n", i, j, dataFile.partAddr[j],
							dataFile.partSize[j], (partAddress + partSize));
				}
			}
		}
		return iff;
	}

	private void readBlockIndex(ByteFileReader in) throws IOException {
		int chunkNum = in.read4BytesAsInt();
		int chunkType = in.readBytesAsInt(2);
		long chunkSizeMod = in.readBytesAsLong(2);
		long flagAddr = in.read4Bytes();
		long chunkSize = calcIndexChunkSize(chunkSizeMod, chunkType);
		long baseAddr = 0;
		IffBlock block = iff.blocks[iff.indexedBlock];
		if (chunkType == 0x80BF) {
			chunkSize += 0x20;
		}
		if (flagAddr == 0x00) {
			baseAddr = chunkSize;
			block.chunkAddr = baseAddr;
			chunkNum--;
			if (IS_DEBUG) {
				System.out.printf("read:%d - (%X-%X) = %X  - %X - %X\n", 0, chunkType, chunkSizeMod, chunkSize,
						flagAddr, chunkSize);
			}
		}
		int chunkTypes[] = new int[chunkNum];
		long chunkAddrs[] = new long[chunkNum];
		long chunkSizes[] = new long[chunkNum];
		long flagAddrs[] = new long[chunkNum];
		int loaded = 0;
		if (flagAddr != 0x00) {
			chunkTypes[0] = chunkType;
			chunkSizes[0] = chunkSize;
			flagAddrs[0] = flagAddr;
			chunkAddrs[0] = block.blockAddr + baseAddr;
			loaded++;
			if (IS_DEBUG) {
				System.out.printf("define:%d - (%X-%X) = %X  - %X - %X\n", 0, chunkType, chunkSizeMod, chunkSize,
						flagAddr, chunkAddrs[0]);
			}
		}
		block.chunkNum = chunkNum;
		Map<Long, IffBlockIndex> indexMap = new HashMap<Long, IffBlockIndex>();
		block.blockIndexList = new ArrayList<IffBlockIndex>();
		for (int j = loaded; j < chunkNum; j++) {
			chunkType = in.readBytesAsInt(2);
			chunkSizeMod = in.readBytesAsLong(2);
			flagAddr = in.read4Bytes();
			long chunkAddr = -1;
			chunkSize = calcIndexChunkSize(chunkSizeMod, chunkType);
			if (j == 0) {
				chunkAddr = block.blockAddr + baseAddr;
			} else {
				chunkAddr = chunkAddrs[j - 1] + chunkSizes[j - 1];
			}
			long key = flagAddr + chunkSize;
			IffBlockIndex index = indexMap.get(key);
			if (index == null) {
				index = new IffBlockIndex();
			} else {
				indexMap.remove(key);
			}
			indexMap.put(flagAddr, index);
			index.addIndex(chunkType, flagAddr, chunkAddr, chunkSize);
			chunkTypes[j] = chunkType;
			chunkSizes[j] = chunkSize;
			flagAddrs[j] = flagAddr;
			chunkAddrs[j] = chunkAddr;
			if (IS_DEBUG) {
				System.out.printf("define:%d - (%X-%X) = %X  - %X - %X\n", j, chunkTypes[j], chunkSizeMod,
						chunkSizes[j], flagAddrs[j], chunkAddrs[j]);
			}
		}
		/*if (IS_DEBUG) {
			for (Long key : indexMap.keySet()) {
				IffBlockIndex index = indexMap.get(key);
				System.out.printf("%X - %S\n", key, index);
			}
		}*/
		block.indexMap = indexMap;
	}

	private long calcIndexChunkSize(long chunkSize, int chunkType) {
		chunkSize *= 0x40;
		switch (chunkType) {
		case 0xE01F:
			chunkSize += 0x38;
			break;
		case 0xD01F:
			chunkSize += 0x34;
			break;
		case 0xC01F:
			chunkSize += 0x30;
			break;
		case 0xB01F:
			chunkSize += 0x2C;
			break;
		case 0xA01F:
			chunkSize += 0x28;
			break;
		case 0x901F:
			chunkSize += 0x24;
			break;
		case 0x801F:
			chunkSize += 0x20;
			break;
		case 0x701F:
			chunkSize += 0x1C;
			break;
		case 0x601F:
			chunkSize += 0x18;
			break;
		case 0x501F:
			chunkSize += 0x14;
			break;
		case 0x401F:
			chunkSize += 0x10;
			break;
		case 0xBF:
			break;
		}
		return chunkSize;
	}
}
